Android开源音乐播放器之播放器基本功能

您所在的位置:网站首页 推荐一款本地音乐播放器 有播放进度 Android开源音乐播放器之播放器基本功能

Android开源音乐播放器之播放器基本功能

2023-07-13 21:43| 来源: 网络整理| 查看: 265

系列文章 Android开源在线音乐播放器——波尼音乐 Android开源音乐播放器之播放器基本功能 Android开源音乐播放器之高仿云音乐黑胶唱片 Android开源音乐播放器之自动滚动歌词 Android开源音乐播放器之在线音乐列表自动加载更多 前言

音乐播放器是我们最常用的应用之一,也是每部手机都会预装的应用。作为一个合格的音乐播放器,应该具有哪些功能呢?“无非是播放、暂停、切换歌曲、进度调节、切换播放模式、专辑封面显示、歌词显示、歌曲列表、歌曲管理(由于国产手机大多都是修改过的Android系统,因此系统自带播放器功能也不一样,这里以Android原生播放器为参考)这些功能” 一开始我也是这么认为的,但当我着手做的时候,才发现这些功能远远不够。如手机来电时,音乐需要自动暂停播放,耳机拔出时,同样需要暂停,还要支持耳机线控,等等,这些都是需要我们考虑的。

一个合格的音乐播放器应该具有哪些基本素质?

由于播放、暂停、切换歌曲、进度调节等这些功能过于简单,因此不过多讨论,这里只讨论一些容易被忽略的功能。

扫描本地音乐

扫描歌曲是播放器的基本功能,一般通过ContentProvider配合Media相关类查询系统数据库,获得媒体库中的歌曲信息。

/** * 扫描歌曲 */ public static void scanMusic(Context context, List musicList) { musicList.clear(); Cursor cursor = context.getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String[]{ BaseColumns._ID, MediaStore.Audio.AudioColumns.IS_MUSIC, MediaStore.Audio.AudioColumns.TITLE, MediaStore.Audio.AudioColumns.ARTIST, MediaStore.Audio.AudioColumns.ALBUM, MediaStore.Audio.AudioColumns.ALBUM_ID, MediaStore.Audio.AudioColumns.DATA, MediaStore.Audio.AudioColumns.DISPLAY_NAME, MediaStore.Audio.AudioColumns.SIZE, MediaStore.Audio.AudioColumns.DURATION }, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); if (cursor == null) { return; } while (cursor.moveToNext()) { // 是否为音乐 int isMusic = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC)); if (isMusic == 0) { continue; } long id = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)); // 标题 String title = cursor.getString((cursor.getColumnIndex(MediaStore.Audio.AudioColumns.TITLE))); // 艺术家 String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.ARTIST)); // 专辑 String album = cursor.getString((cursor.getColumnIndex(MediaStore.Audio.AudioColumns.ALBUM))); // 专辑封面id,根据该id可以获得专辑封面图片 long albumId = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.ALBUM_ID)); // 持续时间 long duration = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)); // 音乐文件路径 String path = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DATA)); // 音乐文件名 String fileName = cursor.getString((cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DISPLAY_NAME))); // 音乐文件大小 long fileSize = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE)); Music music = new Music(); music.set... musicList.add(music); } cursor.close(); } /** * 从媒体库加载封面 */ private Bitmap loadCoverFromMediaStore(long albumId) { ContentResolver resolver = mContext.getContentResolver(); Uri uri = MusicUtils.getMediaStoreAlbumCoverUri(albumId); InputStream is; try { is = resolver.openInputStream(uri); } catch (FileNotFoundException ignored) { return null; } BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; return BitmapFactory.decodeStream(is, null, options); }

通过以上方法基本可以获得音乐的所有信息,弊端是依赖于Android系统媒体库,有时新增音乐后没有通知系统扫描,就无法获得该音乐的信息,不够灵活。

避免播放器内存被系统回收

我们都知道Android系统有自动回收内存机制,如果系统内存紧张,就会触发该机制,应用就有可能被回收,不过Android提供了前台机制,保证内存不足时也不会回收该应用。

/** * 播放时启动前台机制 */ public static void showPlay(Music music) { playService.startForeground(NOTIFICATION_ID, buildNotification(playService, music, true)); } /** * 暂停时取消前台机制 */ public static void showPause(Music music) { playService.stopForeground(false); notificationManager.notify(NOTIFICATION_ID, buildNotification(playService, music, false)); } 捕获/丢弃音乐焦点

大家可能不懂这个标题是什么意思,别着急,让我细细道来。 大家有没有试过,如果手机上安装了两个音乐播放器,当一个正在播放的时候,打开第二个播放歌曲,有没有发现第一个自动暂停了? 或者正在听歌时来电话了,音乐暂停了,挂断电话后音乐又继续播放了, 或者收到通知的时候音乐的音量变小了一下又恢复。

“-纳尼!难道不是自动暂停?” “-图样图森破!”

这其实是因为播放器在后台处理了音频焦点的原因。

public class AudioFocusManager implements AudioManager.OnAudioFocusChangeListener { private PlayService mPlayService; private AudioManager mAudioManager; private boolean isPausedByFocusLossTransient; private int mVolumeWhenFocusLossTransientCanDuck; public AudioFocusManager(@NonNull PlayService playService) { mPlayService = playService; mAudioManager = (AudioManager) playService.getSystemService(AUDIO_SERVICE); } /** * 播放音乐前先请求音频焦点 */ public boolean requestAudioFocus() { return mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } /** * 退出播放器后不再占用音频焦点 */ public void abandonAudioFocus() { mAudioManager.abandonAudioFocus(this); } /** * 音频焦点监听回调 */ @Override public void onAudioFocusChange(int focusChange) { int volume; switch (focusChange) { // 重新获得焦点 case AudioManager.AUDIOFOCUS_GAIN: if (!willPlay() && isPausedByFocusLossTransient) { // 通话结束,恢复播放 mPlayService.playPause(); } volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (mVolumeWhenFocusLossTransientCanDuck > 0 && volume == mVolumeWhenFocusLossTransientCanDuck / 2) { // 恢复音量 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolumeWhenFocusLossTransientCanDuck, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE); } isPausedByFocusLossTransient = false; mVolumeWhenFocusLossTransientCanDuck = 0; break; // 永久丢失焦点,如被其他播放器抢占 case AudioManager.AUDIOFOCUS_LOSS: if (willPlay()) { forceStop(); } break; // 短暂丢失焦点,如来电 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: if (willPlay()) { forceStop(); isPausedByFocusLossTransient = true; } break; // 瞬间丢失焦点,如通知 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: // 音量减小为一半 volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (willPlay() && volume > 0) { mVolumeWhenFocusLossTransientCanDuck = volume; mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolumeWhenFocusLossTransientCanDuck / 2, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE); } break; } } private boolean willPlay() { return mPlayService.isPreparing() || mPlayService.isPlaying(); } } 耳机拔出时暂停播放

“-纳尼!难道耳机拔出时不是自动暂停吗?” “-图样……”

private IntentFilter mNoisyFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); public class NoisyAudioStreamReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 耳机拔出时暂停播放 PlayService.startCommand(context, Actions.ACTION_MEDIA_PLAY_PAUSE); } }

播放时注册广播接收器,暂停时取消注册即可。

联动系统媒体中心

这个标题大家可能也不懂,先放张图吧

明白了吧,我的播放器除了播放了一首音乐之外什么都没做,就可以分别在任务管理、锁屏、负一屏控制我的播放器了,是不是感觉碉堡了。 这些图是在我的小米手机上截的,不保证所有手机都有这些控制功能,但是只要你的Android版本是5.0以上,应该都会有媒体中心,无非是表现形式不同。 Android 5.0中新增了MediaSession类,官方说明是

允许与媒体控制器、音量键、媒体按钮和传输控件交互。

一个类包含了媒体控制和线控等功能,是不是很好用。 现在support-v4包加入了MediaSessionCompat用于在低版本上也能使用这个高大上的功能, 但是低版本上并不能实现媒体控制和线控等功能,低版本的线控功能我会在后面讲。

public class MediaSessionManager { private static final String TAG = "MediaSessionManager"; private static final long MEDIA_SESSION_ACTIONS = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SEEK_TO; private PlayService mPlayService; private MediaSessionCompat mMediaSession; public MediaSessionManager(PlayService playService) { mPlayService = playService; setupMediaSession(); } /** * 初始化并激活MediaSession */ private void setupMediaSession() { mMediaSession = new MediaSessionCompat(mPlayService, TAG); mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS | MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS); mMediaSession.setCallback(callback); mMediaSession.setActive(true); } /** * 更新播放状态,播放/暂停/拖动进度条时调用 */ public void updatePlaybackState() { int state = (mPlayService.isPlaying() || mPlayService.isPreparing()) ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED; mMediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setActions(MEDIA_SESSION_ACTIONS) .setState(state, mPlayService.getCurrentPosition(), 1) .build()); } /** * 更新正在播放的音乐信息,切换歌曲时调用 */ public void updateMetaData(Music music) { if (music == null) { mMediaSession.setMetadata(null); return; } MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, music.getTitle()) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, music.getArtist()) .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, music.getAlbum()) .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, music.getArtist()) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, music.getDuration()) .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, CoverLoader.getInstance().loadThumbnail(music)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, AppCache.getMusicList().size()); } mMediaSession.setMetadata(metaData.build()); } /** * 释放MediaSession,退出播放器时调用 */ public void release() { mMediaSession.setCallback(null); mMediaSession.setActive(false); mMediaSession.release(); } private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() { @Override public void onPlay() { mPlayService.playPause(); } @Override public void onPause() { mPlayService.playPause(); } @Override public void onSkipToNext() { mPlayService.next(); } @Override public void onSkipToPrevious() { mPlayService.prev(); } @Override public void onStop() { mPlayService.stop(); } @Override public void onSeekTo(long pos) { mPlayService.seekTo((int) pos); } }; } 耳机线控(适用于API 19及以下)

“-纳尼……” “-Shut up !”

是的,需要我们自己控制。 如果你已经按照上面的方法激活了MediaSession,那么在5.0以上的系统你已经不需要关心线控功能了,但是在5.0以下仍然需要自己监听耳机按键。

public class RemoteControlReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); if (event == null || event.getAction() != KeyEvent.ACTION_UP) { return; } Intent serviceIntent; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_MEDIA_PLAY: case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_HEADSETHOOK: serviceIntent = new Intent(context, PlayService.class); serviceIntent.setAction(Actions.ACTION_MEDIA_PLAY_PAUSE); context.startService(serviceIntent); break; case KeyEvent.KEYCODE_MEDIA_NEXT: serviceIntent = new Intent(context, PlayService.class); serviceIntent.setAction(Actions.ACTION_MEDIA_NEXT); context.startService(serviceIntent); break; case KeyEvent.KEYCODE_MEDIA_PREVIOUS: serviceIntent = new Intent(context, PlayService.class); serviceIntent.setAction(Actions.ACTION_MEDIA_PREVIOUS); context.startService(serviceIntent); break; } } } 总结

感谢你能耐心的看到最后,本文主要讨论了音乐播放器容易忽略的重要功能,如果还有其他的本文没有提到的,请大家不吝赐教。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3